const map = new Map()
const wm = o => map.get(o)
const normalizeValue = v => (typeof v === 'string' ? v : String(v))
const isIterable = o => o != null && typeof o[Symbol.iterator] === 'function'

function normalizeName (name) {
  if (typeof name !== 'string') name = String(name)

  if (/[^a-z0-9\-#$%&'*+.\\^_`|~]/i.test(name)) {
    throw new TypeError('Invalid character in header field name')
  }

  return name.toLowerCase()
}

export default class Headers {
  /**
   * Headers class
   *
   * @param   Object  headers  Response headers
   * @return  Void
   */
  constructor (headers) {
    map.set(this, Object.create(null))

    if (isIterable(headers)) {
      for (let [name, value] of headers) this.append(name, value)
    } else if (headers) {
      for (let name of Object.keys(headers)) this.append(name, headers[name])
    }
  }

  /**
   * Append a value onto existing header
   *
   * @param   String  name   Header name
   * @param   String  value  Header value
   * @return  Void
   */
  append (name, value) {
    let map = wm(this)

    name = normalizeName(name)
    value = normalizeValue(value)

    if (!map[name]) map[name] = []

    map[name].push(value)
  }

  /**
   * Delete all header values given name
   *
   * @param   String  name  Header name
   * @return  Void
   */
  delete (name) {
    delete wm(this)[normalizeName(name)]
  }

  /**
   * Iterate over all headers as [name, value]
   *
   * @return  Iterator
   */
  * entries () {
    let map = wm(this)

    for (let name in map) yield [name, map[name].join(',')]
  }

  /**
   * Return first header value given name
   *
   * @param   String  name  Header name
   * @return  Mixed
   */
  get (name) {
    let map = wm(this)
    name = normalizeName(name)

    return map[name] ? map[name][0] : null
  }

  /**
   * Check for header name existence
   *
   * @param   String   name  Header name
   * @return  Boolean
   */
  has (name) {
    return normalizeName(name) in wm(this)
  }

  /**
   * Iterate over all keys
   *
   * @return  Iterator
   */
  * keys () {
    for (let [name] of this) yield name
  }

  /**
   * Overwrite header values given name
   *
   * @param   String  name   Header name
   * @param   String  value  Header value
   * @return  Void
   */
  set (name, value) {
    wm(this)[normalizeName(name)] = [normalizeValue(value)]
  }

  /**
   * Iterate over all values
   *
   * @return  Iterator
   */
  * values () {
    for (let [, value] of this) yield value
  }

  forEach () {
    return map.forEach
  }

  /**
   * The class itself is iterable
   * alies for headers.entries()
   *
   * @return  Iterator
   */
  [Symbol.iterator] () {
    return this.entries()
  }

  /**
   * Create the default string description.
   * It is accessed internally by the Object.prototype.toString().
   *
   * @return  String  [Object Headers]
   */
  get [Symbol.toStringTag] () {
    return 'Headers'
  }
}
